// © Broadcom. All Rights Reserved.
// The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
// SPDX-License-Identifier: Apache-2.0

package vm

import (
	"context"
	"flag"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/vmware/govmomi/cli"
	"github.com/vmware/govmomi/cli/flags"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/vim25/types"
)

type hidKey struct {
	Code         int32
	ShiftPressed bool
}

// stolen from
// https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2#file-usb_hid_keys-h-L110
const (
	KEY_MOD_LCTRL          = 0x01
	KEY_MOD_LSHIFT         = 0x02
	KEY_MOD_LALT           = 0x04
	KEY_MOD_LMETA          = 0x08
	KEY_MOD_RCTRL          = 0x10
	KEY_MOD_RSHIFT         = 0x20
	KEY_MOD_RALT           = 0x40
	KEY_MOD_RMETA          = 0x80
	KEY_NONE               = 0x00
	KEY_ERR_OVF            = 0x01
	KEY_A                  = 0x04
	KEY_B                  = 0x05
	KEY_C                  = 0x06
	KEY_D                  = 0x07
	KEY_E                  = 0x08
	KEY_F                  = 0x09
	KEY_G                  = 0x0a
	KEY_H                  = 0x0b
	KEY_I                  = 0x0c
	KEY_J                  = 0x0d
	KEY_K                  = 0x0e
	KEY_L                  = 0x0f
	KEY_M                  = 0x10
	KEY_N                  = 0x11
	KEY_O                  = 0x12
	KEY_P                  = 0x13
	KEY_Q                  = 0x14
	KEY_R                  = 0x15
	KEY_S                  = 0x16
	KEY_T                  = 0x17
	KEY_U                  = 0x18
	KEY_V                  = 0x19
	KEY_W                  = 0x1a
	KEY_X                  = 0x1b
	KEY_Y                  = 0x1c
	KEY_Z                  = 0x1d
	KEY_1                  = 0x1e
	KEY_2                  = 0x1f
	KEY_3                  = 0x20
	KEY_4                  = 0x21
	KEY_5                  = 0x22
	KEY_6                  = 0x23
	KEY_7                  = 0x24
	KEY_8                  = 0x25
	KEY_9                  = 0x26
	KEY_0                  = 0x27
	KEY_ENTER              = 0x28
	KEY_ESC                = 0x29
	KEY_BACKSPACE          = 0x2a
	KEY_TAB                = 0x2b
	KEY_SPACE              = 0x2c
	KEY_MINUS              = 0x2d
	KEY_EQUAL              = 0x2e
	KEY_LEFTBRACE          = 0x2f
	KEY_RIGHTBRACE         = 0x30
	KEY_BACKSLASH          = 0x31
	KEY_HASHTILDE          = 0x32
	KEY_SEMICOLON          = 0x33
	KEY_APOSTROPHE         = 0x34
	KEY_GRAVE              = 0x35
	KEY_COMMA              = 0x36
	KEY_DOT                = 0x37
	KEY_SLASH              = 0x38
	KEY_CAPSLOCK           = 0x39
	KEY_F1                 = 0x3a
	KEY_F2                 = 0x3b
	KEY_F3                 = 0x3c
	KEY_F4                 = 0x3d
	KEY_F5                 = 0x3e
	KEY_F6                 = 0x3f
	KEY_F7                 = 0x40
	KEY_F8                 = 0x41
	KEY_F9                 = 0x42
	KEY_F10                = 0x43
	KEY_F11                = 0x44
	KEY_F12                = 0x45
	KEY_SYSRQ              = 0x46
	KEY_SCROLLLOCK         = 0x47
	KEY_PAUSE              = 0x48
	KEY_INSERT             = 0x49
	KEY_HOME               = 0x4a
	KEY_PAGEUP             = 0x4b
	KEY_DELETE             = 0x4c
	KEY_END                = 0x4d
	KEY_PAGEDOWN           = 0x4e
	KEY_RIGHT              = 0x4f
	KEY_LEFT               = 0x50
	KEY_DOWN               = 0x51
	KEY_UP                 = 0x52
	KEY_NUMLOCK            = 0x53
	KEY_KPSLASH            = 0x54
	KEY_KPASTERISK         = 0x55
	KEY_KPMINUS            = 0x56
	KEY_KPPLUS             = 0x57
	KEY_KPENTER            = 0x58
	KEY_KP1                = 0x59
	KEY_KP2                = 0x5a
	KEY_KP3                = 0x5b
	KEY_KP4                = 0x5c
	KEY_KP5                = 0x5d
	KEY_KP6                = 0x5e
	KEY_KP7                = 0x5f
	KEY_KP8                = 0x60
	KEY_KP9                = 0x61
	KEY_KP0                = 0x62
	KEY_KPDOT              = 0x63
	KEY_102ND              = 0x64
	KEY_COMPOSE            = 0x65
	KEY_POWER              = 0x66
	KEY_KPEQUAL            = 0x67
	KEY_F13                = 0x68
	KEY_F14                = 0x69
	KEY_F15                = 0x6a
	KEY_F16                = 0x6b
	KEY_F17                = 0x6c
	KEY_F18                = 0x6d
	KEY_F19                = 0x6e
	KEY_F20                = 0x6f
	KEY_F21                = 0x70
	KEY_F22                = 0x71
	KEY_F23                = 0x72
	KEY_F24                = 0x73
	KEY_OPEN               = 0x74
	KEY_HELP               = 0x75
	KEY_PROPS              = 0x76
	KEY_FRONT              = 0x77
	KEY_STOP               = 0x78
	KEY_AGAIN              = 0x79
	KEY_UNDO               = 0x7a
	KEY_CUT                = 0x7b
	KEY_COPY               = 0x7c
	KEY_PASTE              = 0x7d
	KEY_FIND               = 0x7e
	KEY_MUTE               = 0x7f
	KEY_VOLUMEUP           = 0x80
	KEY_VOLUMEDOWN         = 0x81
	KEY_KPCOMMA            = 0x85
	KEY_RO                 = 0x87
	KEY_KATAKANAHIRAGANA   = 0x88
	KEY_YEN                = 0x89
	KEY_HENKAN             = 0x8a
	KEY_MUHENKAN           = 0x8b
	KEY_KPJPCOMMA          = 0x8c
	KEY_HANGEUL            = 0x90
	KEY_HANJA              = 0x91
	KEY_KATAKANA           = 0x92
	KEY_HIRAGANA           = 0x93
	KEY_ZENKAKUHANKAKU     = 0x94
	KEY_KPLEFTPAREN        = 0xb6
	KEY_KPRIGHTPAREN       = 0xb7
	KEY_LEFTCTRL           = 0xe0
	KEY_LEFTSHIFT          = 0xe1
	KEY_LEFTALT            = 0xe2
	KEY_LEFTMETA           = 0xe3
	KEY_RIGHTCTRL          = 0xe4
	KEY_RIGHTSHIFT         = 0xe5
	KEY_RIGHTALT           = 0xe6
	KEY_RIGHTMETA          = 0xe7
	KEY_MEDIA_PLAYPAUSE    = 0xe8
	KEY_MEDIA_STOPCD       = 0xe9
	KEY_MEDIA_PREVIOUSSONG = 0xea
	KEY_MEDIA_NEXTSONG     = 0xeb
	KEY_MEDIA_EJECTCD      = 0xec
	KEY_MEDIA_VOLUMEUP     = 0xed
	KEY_MEDIA_VOLUMEDOWN   = 0xee
	KEY_MEDIA_MUTE         = 0xef
	KEY_MEDIA_WWW          = 0xf0
	KEY_MEDIA_BACK         = 0xf1
	KEY_MEDIA_FORWARD      = 0xf2
	KEY_MEDIA_STOP         = 0xf3
	KEY_MEDIA_FIND         = 0xf4
	KEY_MEDIA_SCROLLUP     = 0xf5
	KEY_MEDIA_SCROLLDOWN   = 0xf6
	KEY_MEDIA_EDIT         = 0xf7
	KEY_MEDIA_SLEEP        = 0xf8
	KEY_MEDIA_COFFEE       = 0xf9
	KEY_MEDIA_REFRESH      = 0xfa
	KEY_MEDIA_CALC         = 0xfb
)

var hidKeyMap = map[string]int32{
	"KEY_MOD_LCTRL":          KEY_MOD_LCTRL,
	"KEY_MOD_LSHIFT":         KEY_MOD_LSHIFT,
	"KEY_MOD_LALT":           KEY_MOD_LALT,
	"KEY_MOD_LMETA":          KEY_MOD_LMETA,
	"KEY_MOD_RCTRL":          KEY_MOD_RCTRL,
	"KEY_MOD_RSHIFT":         KEY_MOD_RSHIFT,
	"KEY_MOD_RALT":           KEY_MOD_RALT,
	"KEY_MOD_RMETA":          KEY_MOD_RMETA,
	"KEY_NONE":               KEY_NONE,
	"KEY_ERR_OVF":            KEY_ERR_OVF,
	"KEY_A":                  KEY_A,
	"KEY_B":                  KEY_B,
	"KEY_C":                  KEY_C,
	"KEY_D":                  KEY_D,
	"KEY_E":                  KEY_E,
	"KEY_F":                  KEY_F,
	"KEY_G":                  KEY_G,
	"KEY_H":                  KEY_H,
	"KEY_I":                  KEY_I,
	"KEY_J":                  KEY_J,
	"KEY_K":                  KEY_K,
	"KEY_L":                  KEY_L,
	"KEY_M":                  KEY_M,
	"KEY_N":                  KEY_N,
	"KEY_O":                  KEY_O,
	"KEY_P":                  KEY_P,
	"KEY_Q":                  KEY_Q,
	"KEY_R":                  KEY_R,
	"KEY_S":                  KEY_S,
	"KEY_T":                  KEY_T,
	"KEY_U":                  KEY_U,
	"KEY_V":                  KEY_V,
	"KEY_W":                  KEY_W,
	"KEY_X":                  KEY_X,
	"KEY_Y":                  KEY_Y,
	"KEY_Z":                  KEY_Z,
	"KEY_1":                  KEY_1,
	"KEY_2":                  KEY_2,
	"KEY_3":                  KEY_3,
	"KEY_4":                  KEY_4,
	"KEY_5":                  KEY_5,
	"KEY_6":                  KEY_6,
	"KEY_7":                  KEY_7,
	"KEY_8":                  KEY_8,
	"KEY_9":                  KEY_9,
	"KEY_0":                  KEY_0,
	"KEY_ENTER":              KEY_ENTER,
	"KEY_ESC":                KEY_ESC,
	"KEY_BACKSPACE":          KEY_BACKSPACE,
	"KEY_TAB":                KEY_TAB,
	"KEY_SPACE":              KEY_SPACE,
	"KEY_MINUS":              KEY_MINUS,
	"KEY_EQUAL":              KEY_EQUAL,
	"KEY_LEFTBRACE":          KEY_LEFTBRACE,
	"KEY_RIGHTBRACE":         KEY_RIGHTBRACE,
	"KEY_BACKSLASH":          KEY_BACKSLASH,
	"KEY_HASHTILDE":          KEY_HASHTILDE,
	"KEY_SEMICOLON":          KEY_SEMICOLON,
	"KEY_APOSTROPHE":         KEY_APOSTROPHE,
	"KEY_GRAVE":              KEY_GRAVE,
	"KEY_COMMA":              KEY_COMMA,
	"KEY_DOT":                KEY_DOT,
	"KEY_SLASH":              KEY_SLASH,
	"KEY_CAPSLOCK":           KEY_CAPSLOCK,
	"KEY_F1":                 KEY_F1,
	"KEY_F2":                 KEY_F2,
	"KEY_F3":                 KEY_F3,
	"KEY_F4":                 KEY_F4,
	"KEY_F5":                 KEY_F5,
	"KEY_F6":                 KEY_F6,
	"KEY_F7":                 KEY_F7,
	"KEY_F8":                 KEY_F8,
	"KEY_F9":                 KEY_F9,
	"KEY_F10":                KEY_F10,
	"KEY_F11":                KEY_F11,
	"KEY_F12":                KEY_F12,
	"KEY_SYSRQ":              KEY_SYSRQ,
	"KEY_SCROLLLOCK":         KEY_SCROLLLOCK,
	"KEY_PAUSE":              KEY_PAUSE,
	"KEY_INSERT":             KEY_INSERT,
	"KEY_HOME":               KEY_HOME,
	"KEY_PAGEUP":             KEY_PAGEUP,
	"KEY_DELETE":             KEY_DELETE,
	"KEY_END":                KEY_END,
	"KEY_PAGEDOWN":           KEY_PAGEDOWN,
	"KEY_RIGHT":              KEY_RIGHT,
	"KEY_LEFT":               KEY_LEFT,
	"KEY_DOWN":               KEY_DOWN,
	"KEY_UP":                 KEY_UP,
	"KEY_NUMLOCK":            KEY_NUMLOCK,
	"KEY_KPSLASH":            KEY_KPSLASH,
	"KEY_KPASTERISK":         KEY_KPASTERISK,
	"KEY_KPMINUS":            KEY_KPMINUS,
	"KEY_KPPLUS":             KEY_KPPLUS,
	"KEY_KPENTER":            KEY_KPENTER,
	"KEY_KP1":                KEY_KP1,
	"KEY_KP2":                KEY_KP2,
	"KEY_KP3":                KEY_KP3,
	"KEY_KP4":                KEY_KP4,
	"KEY_KP5":                KEY_KP5,
	"KEY_KP6":                KEY_KP6,
	"KEY_KP7":                KEY_KP7,
	"KEY_KP8":                KEY_KP8,
	"KEY_KP9":                KEY_KP9,
	"KEY_KP0":                KEY_KP0,
	"KEY_KPDOT":              KEY_KPDOT,
	"KEY_102ND":              KEY_102ND,
	"KEY_COMPOSE":            KEY_COMPOSE,
	"KEY_POWER":              KEY_POWER,
	"KEY_KPEQUAL":            KEY_KPEQUAL,
	"KEY_F13":                KEY_F13,
	"KEY_F14":                KEY_F14,
	"KEY_F15":                KEY_F15,
	"KEY_F16":                KEY_F16,
	"KEY_F17":                KEY_F17,
	"KEY_F18":                KEY_F18,
	"KEY_F19":                KEY_F19,
	"KEY_F20":                KEY_F20,
	"KEY_F21":                KEY_F21,
	"KEY_F22":                KEY_F22,
	"KEY_F23":                KEY_F23,
	"KEY_F24":                KEY_F24,
	"KEY_OPEN":               KEY_OPEN,
	"KEY_HELP":               KEY_HELP,
	"KEY_PROPS":              KEY_PROPS,
	"KEY_FRONT":              KEY_FRONT,
	"KEY_STOP":               KEY_STOP,
	"KEY_AGAIN":              KEY_AGAIN,
	"KEY_UNDO":               KEY_UNDO,
	"KEY_CUT":                KEY_CUT,
	"KEY_COPY":               KEY_COPY,
	"KEY_PASTE":              KEY_PASTE,
	"KEY_FIND":               KEY_FIND,
	"KEY_MUTE":               KEY_MUTE,
	"KEY_VOLUMEUP":           KEY_VOLUMEUP,
	"KEY_VOLUMEDOWN":         KEY_VOLUMEDOWN,
	"KEY_KPCOMMA":            KEY_KPCOMMA,
	"KEY_RO":                 KEY_RO,
	"KEY_KATAKANAHIRAGANA":   KEY_KATAKANAHIRAGANA,
	"KEY_YEN":                KEY_YEN,
	"KEY_HENKAN":             KEY_HENKAN,
	"KEY_MUHENKAN":           KEY_MUHENKAN,
	"KEY_KPJPCOMMA":          KEY_KPJPCOMMA,
	"KEY_HANGEUL":            KEY_HANGEUL,
	"KEY_HANJA":              KEY_HANJA,
	"KEY_KATAKANA":           KEY_KATAKANA,
	"KEY_HIRAGANA":           KEY_HIRAGANA,
	"KEY_ZENKAKUHANKAKU":     KEY_ZENKAKUHANKAKU,
	"KEY_KPLEFTPAREN":        KEY_KPLEFTPAREN,
	"KEY_KPRIGHTPAREN":       KEY_KPRIGHTPAREN,
	"KEY_LEFTCTRL":           KEY_LEFTCTRL,
	"KEY_LEFTSHIFT":          KEY_LEFTSHIFT,
	"KEY_LEFTALT":            KEY_LEFTALT,
	"KEY_LEFTMETA":           KEY_LEFTMETA,
	"KEY_RIGHTCTRL":          KEY_RIGHTCTRL,
	"KEY_RIGHTSHIFT":         KEY_RIGHTSHIFT,
	"KEY_RIGHTALT":           KEY_RIGHTALT,
	"KEY_RIGHTMETA":          KEY_RIGHTMETA,
	"KEY_MEDIA_PLAYPAUSE":    KEY_MEDIA_PLAYPAUSE,
	"KEY_MEDIA_STOPCD":       KEY_MEDIA_STOPCD,
	"KEY_MEDIA_PREVIOUSSONG": KEY_MEDIA_PREVIOUSSONG,
	"KEY_MEDIA_NEXTSONG":     KEY_MEDIA_NEXTSONG,
	"KEY_MEDIA_EJECTCD":      KEY_MEDIA_EJECTCD,
	"KEY_MEDIA_VOLUMEUP":     KEY_MEDIA_VOLUMEUP,
	"KEY_MEDIA_VOLUMEDOWN":   KEY_MEDIA_VOLUMEDOWN,
	"KEY_MEDIA_MUTE":         KEY_MEDIA_MUTE,
	"KEY_MEDIA_WWW":          KEY_MEDIA_WWW,
	"KEY_MEDIA_BACK":         KEY_MEDIA_BACK,
	"KEY_MEDIA_FORWARD":      KEY_MEDIA_FORWARD,
	"KEY_MEDIA_STOP":         KEY_MEDIA_STOP,
	"KEY_MEDIA_FIND":         KEY_MEDIA_FIND,
	"KEY_MEDIA_SCROLLUP":     KEY_MEDIA_SCROLLUP,
	"KEY_MEDIA_SCROLLDOWN":   KEY_MEDIA_SCROLLDOWN,
	"KEY_MEDIA_EDIT":         KEY_MEDIA_EDIT,
	"KEY_MEDIA_SLEEP":        KEY_MEDIA_SLEEP,
	"KEY_MEDIA_COFFEE":       KEY_MEDIA_COFFEE,
	"KEY_MEDIA_REFRESH":      KEY_MEDIA_REFRESH,
	"KEY_MEDIA_CALC":         KEY_MEDIA_CALC,
}

var hidCharacterMap = map[string]hidKey{
	"a": {KEY_A, false},
	"b": {KEY_B, false},
	"c": {KEY_C, false},
	"d": {KEY_D, false},
	"e": {KEY_E, false},
	"f": {KEY_F, false},
	"g": {KEY_G, false},
	"h": {KEY_H, false},
	"i": {KEY_I, false},
	"j": {KEY_J, false},
	"k": {KEY_K, false},
	"l": {KEY_L, false},
	"m": {KEY_M, false},
	"n": {KEY_N, false},
	"o": {KEY_O, false},
	"p": {KEY_P, false},
	"q": {KEY_Q, false},
	"r": {KEY_R, false},
	"s": {KEY_S, false},
	"t": {KEY_T, false},
	"u": {KEY_U, false},
	"v": {KEY_V, false},
	"w": {KEY_W, false},
	"x": {KEY_X, false},
	"y": {KEY_Y, false},
	"z": {KEY_Z, false},
	"1": {KEY_1, false},
	"2": {KEY_2, false},
	"3": {KEY_3, false},
	"4": {KEY_4, false},
	"5": {KEY_5, false},
	"6": {KEY_6, false},
	"7": {KEY_7, false},
	"8": {KEY_8, false},
	"9": {KEY_9, false},
	"0": {KEY_0, false},
	"A": {KEY_A, true},
	"B": {KEY_B, true},
	"C": {KEY_C, true},
	"D": {KEY_D, true},
	"E": {KEY_E, true},
	"F": {KEY_F, true},
	"G": {KEY_G, true},
	"H": {KEY_H, true},
	"I": {KEY_I, true},
	"J": {KEY_J, true},
	"K": {KEY_K, true},
	"L": {KEY_L, true},
	"M": {KEY_M, true},
	"N": {KEY_N, true},
	"O": {KEY_O, true},
	"P": {KEY_P, true},
	"Q": {KEY_Q, true},
	"R": {KEY_R, true},
	"S": {KEY_S, true},
	"T": {KEY_T, true},
	"U": {KEY_U, true},
	"V": {KEY_V, true},
	"W": {KEY_W, true},
	"X": {KEY_X, true},
	"Y": {KEY_Y, true},
	"Z": {KEY_Z, true},
	"!": {KEY_1, true},
	"@": {KEY_2, true},
	"#": {KEY_3, true},
	"$": {KEY_4, true},
	"%": {KEY_5, true},
	"^": {KEY_6, true},
	"&": {KEY_7, true},
	"*": {KEY_8, true},
	"(": {KEY_9, true},
	")": {KEY_0, true},
	" ": {KEY_SPACE, false},
	"-": {KEY_MINUS, false},
	"_": {KEY_MINUS, true},
	"=": {KEY_EQUAL, false},
	"+": {KEY_EQUAL, true},
	"[": {KEY_LEFTBRACE, false},
	"{": {KEY_LEFTBRACE, true},
	"]": {KEY_RIGHTBRACE, false},
	"}": {KEY_RIGHTBRACE, true},
	`\`: {KEY_BACKSLASH, false},
	"|": {KEY_BACKSLASH, true},
	";": {KEY_SEMICOLON, false},
	":": {KEY_SEMICOLON, true},
	"'": {KEY_APOSTROPHE, false},
	`"`: {KEY_APOSTROPHE, true},
	"`": {KEY_GRAVE, false},
	"~": {KEY_GRAVE, true},
	",": {KEY_COMMA, false},
	"<": {KEY_COMMA, true},
	".": {KEY_DOT, false},
	">": {KEY_DOT, true},
	"/": {KEY_SLASH, false},
	"?": {KEY_SLASH, true},
}

type keystrokes struct {
	*flags.VirtualMachineFlag

	UsbHidCodeValue int32
	UsbHidCodes     string
	UsbHidString    string
	LeftControl     bool
	LeftShift       bool
	LeftAlt         bool
	LeftGui         bool
	RightControl    bool
	RightShift      bool
	RightAlt        bool
	RightGui        bool
}

func init() {
	cli.Register("vm.keystrokes", &keystrokes{})
}

func (cmd *keystrokes) Register(ctx context.Context, f *flag.FlagSet) {
	cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx)
	cmd.VirtualMachineFlag.Register(ctx, f)

	f.StringVar(&cmd.UsbHidString, "s", "", "Raw String to Send")
	f.StringVar(&cmd.UsbHidCodes, "c", "", "USB HID Codes (hex) or aliases, comma separated")
	f.Var(flags.NewInt32(&cmd.UsbHidCodeValue), "r", "Raw USB HID Code Value (int32)")
	f.BoolVar(&cmd.LeftControl, "lc", false, "Enable/Disable Left Control")
	f.BoolVar(&cmd.LeftShift, "ls", false, "Enable/Disable Left Shift")
	f.BoolVar(&cmd.LeftAlt, "la", false, "Enable/Disable Left Alt")
	f.BoolVar(&cmd.LeftGui, "lg", false, "Enable/Disable Left Gui")
	f.BoolVar(&cmd.RightControl, "rc", false, "Enable/Disable Right Control")
	f.BoolVar(&cmd.RightShift, "rs", false, "Enable/Disable Right Shift")
	f.BoolVar(&cmd.RightAlt, "ra", false, "Enable/Disable Right Alt")
	f.BoolVar(&cmd.RightGui, "rg", false, "Enable/Disable Right Gui")
}

func (cmd *keystrokes) Usage() string {
	return "VM"
}

func (cmd *keystrokes) Description() string {
	description := `Send Keystrokes to VM.

Examples:
 Default Scenario
  govc vm.keystrokes -vm $vm -s "root" 	# writes 'root' to the console
  govc vm.keystrokes -vm $vm -c 0x15 	# writes an 'r' to the console
  govc vm.keystrokes -vm $vm -r 1376263 # writes an 'r' to the console
  govc vm.keystrokes -vm $vm -c 0x28 	# presses ENTER on the console
  govc vm.keystrokes -vm $vm -c 0x4c -la=true -lc=true 	# sends CTRL+ALT+DEL to console
  govc vm.keystrokes -vm $vm -c 0x15,KEY_ENTER # writes an 'r' to the console and press ENTER

List of available aliases:
`
	keys := make([]string, 0)
	for key, _ := range hidKeyMap {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for i, key := range keys {
		if i > 0 {
			description += ", "
		}
		description += key
	}
	return description + "\n"
}

func (cmd *keystrokes) Process(ctx context.Context) error {
	if err := cmd.VirtualMachineFlag.Process(ctx); err != nil {
		return err
	}
	return nil
}

func (cmd *keystrokes) Run(ctx context.Context, f *flag.FlagSet) error {
	vm, err := cmd.VirtualMachine()
	if err != nil {
		return err
	}

	if vm == nil {
		return flag.ErrHelp
	}

	err = cmd.processUserInput(ctx, vm)
	if err != nil {
		return err
	}
	return nil
}

func (cmd *keystrokes) processUserInput(ctx context.Context, vm *object.VirtualMachine) error {
	if err := cmd.checkValidInputs(); err != nil {
		return err
	}

	codes, err := cmd.processUsbCode()

	if err != nil {
		return err
	}

	var keyEventArray []types.UsbScanCodeSpecKeyEvent
	for _, code := range codes {
		leftShiftSetting := false
		if code.ShiftPressed || cmd.LeftShift {
			leftShiftSetting = true
		}
		modifiers := types.UsbScanCodeSpecModifierType{
			LeftControl:  &cmd.LeftControl,
			LeftShift:    &leftShiftSetting,
			LeftAlt:      &cmd.LeftAlt,
			LeftGui:      &cmd.LeftGui,
			RightControl: &cmd.RightControl,
			RightShift:   &cmd.RightShift,
			RightAlt:     &cmd.RightAlt,
			RightGui:     &cmd.RightGui,
		}
		keyEvent := types.UsbScanCodeSpecKeyEvent{
			UsbHidCode: code.Code,
			Modifiers:  &modifiers,
		}
		keyEventArray = append(keyEventArray, keyEvent)
	}

	spec := types.UsbScanCodeSpec{
		KeyEvents: keyEventArray,
	}

	_, err = vm.PutUsbScanCodes(ctx, spec)

	return err
}

func (cmd *keystrokes) processUsbCode() ([]hidKey, error) {
	if cmd.rawCodeProvided() {
		return []hidKey{{cmd.UsbHidCodeValue, false}}, nil
	}

	if cmd.hexCodeProvided() {
		var retKeyArray []hidKey
		for _, c := range strings.Split(cmd.UsbHidCodes, ",") {
			var s int32
			lookupvalue, ok := hidKeyMap[c]
			if ok {
				s = intToHidCode(lookupvalue)
			} else {
				var err error
				s, err = hexStringToHidCode(c)
				if err != nil {
					return nil, err
				}
			}
			retKeyArray = append(retKeyArray, hidKey{s, false})
		}
		return retKeyArray, nil
	}

	if cmd.stringProvided() {
		var retKeyArray []hidKey
		for _, c := range cmd.UsbHidString {
			lookupValue, ok := hidCharacterMap[string(c)]
			if !ok {
				return nil, fmt.Errorf("invalid Character %s in String: %s", string(c), cmd.UsbHidString)
			}
			lookupValue.Code = intToHidCode(lookupValue.Code)
			retKeyArray = append(retKeyArray, lookupValue)
		}
		return retKeyArray, nil
	}
	return nil, nil
}

func hexStringToHidCode(hex string) (int32, error) {
	s, err := strconv.ParseInt(hex, 0, 32)
	if err != nil {
		return 0, err
	}
	return intToHidCode(int32(s)), nil
}

func intToHidCode(v int32) int32 {
	var s int32 = v << 16
	s |= 7
	return s
}

func (cmd *keystrokes) checkValidInputs() error {
	// poor man's boolean XOR -> A xor B xor C = A'BC' + AB'C' + A'B'C + ABC
	if (!cmd.rawCodeProvided() && cmd.hexCodeProvided() && !cmd.stringProvided()) || // A'BC'
		(cmd.rawCodeProvided() && !cmd.hexCodeProvided() && !cmd.stringProvided()) || // AB'C'
		(!cmd.rawCodeProvided() && !cmd.hexCodeProvided() && cmd.stringProvided()) || // A'B'C
		(cmd.rawCodeProvided() && cmd.hexCodeProvided() && cmd.stringProvided()) { // ABC
		return nil
	}
	return fmt.Errorf("specify only 1 argument")
}

func (cmd keystrokes) rawCodeProvided() bool {
	return cmd.UsbHidCodeValue != 0
}

func (cmd keystrokes) hexCodeProvided() bool {
	return cmd.UsbHidCodes != ""
}

func (cmd keystrokes) stringProvided() bool {
	return cmd.UsbHidString != ""
}
